Pelajari cara memanfaatkan mapped types TypeScript untuk mengubah bentuk objek secara dinamis, memungkinkan kode yang kuat dan mudah dipelihara untuk aplikasi global.
Mapped Types TypeScript untuk Transformasi Objek Dinamis: Panduan Komprehensif
TypeScript, dengan penekanan kuat pada pengetikan statis, memberdayakan pengembang untuk menulis kode yang lebih andal dan mudah dipelihara. Fitur penting yang berkontribusi signifikan terhadap hal ini adalah mapped types. Panduan ini mendalami dunia mapped types TypeScript, memberikan pemahaman komprehensif tentang fungsionalitas, manfaat, dan aplikasi praktisnya, terutama dalam konteks pengembangan solusi perangkat lunak global.
Memahami Konsep Inti
Pada intinya, mapped type memungkinkan Anda untuk membuat tipe baru berdasarkan properti dari tipe yang sudah ada. Anda mendefinisikan tipe baru dengan melakukan iterasi pada kunci (keys) dari tipe lain dan menerapkan transformasi pada nilainya. Ini sangat berguna untuk skenario di mana Anda perlu memodifikasi struktur objek secara dinamis, seperti mengubah tipe data properti, menjadikan properti opsional, atau menambahkan properti baru berdasarkan yang sudah ada.
Mari kita mulai dengan dasar-dasarnya. Perhatikan antarmuka (interface) sederhana berikut:
interface Person {
name: string;
age: number;
email: string;
}
Sekarang, mari kita definisikan mapped type yang membuat semua properti dari Person
menjadi opsional:
type OptionalPerson = {
[K in keyof Person]?: Person[K];
};
Dalam contoh ini:
[K in keyof Person]
melakukan iterasi melalui setiap kunci (name
,age
,email
) dari antarmukaPerson
.?
membuat setiap properti menjadi opsional.Person[K]
mengacu pada tipe properti di antarmukaPerson
yang asli.
Tipe OptionalPerson
yang dihasilkan secara efektif akan terlihat seperti ini:
{
name?: string;
age?: number;
email?: string;
}
Ini menunjukkan kekuatan mapped types untuk memodifikasi tipe yang sudah ada secara dinamis.
Sintaksis dan Struktur Mapped Types
Sintaksis dari mapped type cukup spesifik dan mengikuti struktur umum ini:
type NewType = {
[Key in KeysType]: ValueType;
};
Mari kita uraikan setiap komponen:
NewType
: Nama yang Anda berikan untuk tipe baru yang sedang dibuat.[Key in KeysType]
: Ini adalah inti dari mapped type.Key
adalah variabel yang melakukan iterasi melalui setiap anggota dariKeysType
.KeysType
sering kali, tetapi tidak selalu, adalahkeyof
dari tipe lain (seperti dalam contohOptionalPerson
kita). Ini juga bisa berupa gabungan (union) dari string literal atau tipe yang lebih kompleks.ValueType
: Ini menentukan tipe dari properti dalam tipe baru. Ini bisa berupa tipe langsung (sepertistring
), tipe berdasarkan properti tipe asli (sepertiPerson[K]
), atau transformasi yang lebih kompleks dari tipe asli.
Contoh: Mengubah Tipe Properti
Bayangkan Anda perlu mengubah semua properti numerik dari sebuah objek menjadi string. Begini cara Anda melakukannya menggunakan mapped type:
interface Product {
id: number;
name: string;
price: number;
quantity: number;
}
type StringifiedProduct = {
[K in keyof Product]: Product[K] extends number ? string : Product[K];
};
Dalam kasus ini, kita:
- Melakukan iterasi melalui setiap kunci dari antarmuka
Product
. - Menggunakan tipe kondisional (
Product[K] extends number ? string : Product[K]
) untuk memeriksa apakah properti tersebut adalah angka. - Jika itu adalah angka, kita mengatur tipe properti menjadi
string
; jika tidak, kita mempertahankan tipe aslinya.
Tipe StringifiedProduct
yang dihasilkan akan menjadi:
{
id: string;
name: string;
price: string;
quantity: string;
}
Fitur dan Teknik Utama
1. Menggunakan keyof
dan Index Signatures
Seperti yang telah ditunjukkan sebelumnya, keyof
adalah alat fundamental untuk bekerja dengan mapped types. Ini memungkinkan Anda untuk melakukan iterasi pada kunci-kunci dari sebuah tipe. Index signatures menyediakan cara untuk mendefinisikan tipe properti ketika Anda tidak mengetahui kunci-kuncinya terlebih dahulu, tetapi Anda tetap ingin mengubahnya.
Contoh: Mengubah semua properti berdasarkan index signature
interface StringMap {
[key: string]: number;
}
type StringMapToString = {
[K in keyof StringMap]: string;
};
Di sini, semua nilai numerik dalam StringMap diubah menjadi string di dalam tipe baru.
2. Tipe Kondisional dalam Mapped Types
Tipe kondisional adalah fitur canggih dari TypeScript yang memungkinkan Anda untuk menyatakan hubungan tipe berdasarkan kondisi. Ketika digabungkan dengan mapped types, mereka memungkinkan transformasi yang sangat canggih.
Contoh: Menghapus Null dan Undefined dari sebuah tipe
type NonNullableProperties = {
[K in keyof T]: T[K] extends (null | undefined) ? never : T[K];
};
Mapped type ini melakukan iterasi melalui semua kunci dari tipe T
dan menggunakan tipe kondisional untuk memeriksa apakah nilainya mengizinkan null atau undefined. Jika ya, maka tipe tersebut akan dievaluasi menjadi never, yang secara efektif menghapus properti tersebut; jika tidak, ia akan mempertahankan tipe aslinya. Pendekatan ini membuat tipe menjadi lebih kuat dengan mengecualikan nilai null atau undefined yang berpotensi menimbulkan masalah, meningkatkan kualitas kode, dan selaras dengan praktik terbaik untuk pengembangan perangkat lunak global.
3. Utility Types untuk Efisiensi
TypeScript menyediakan utility types bawaan yang menyederhanakan tugas manipulasi tipe yang umum. Tipe-tipe ini memanfaatkan mapped types di belakang layar.
Partial
: Membuat semua properti dari tipeT
menjadi opsional (seperti yang ditunjukkan pada contoh sebelumnya).Required
: Membuat semua properti dari tipeT
menjadi wajib.Readonly
: Membuat semua properti dari tipeT
menjadi read-only (hanya bisa dibaca).Pick
: Membuat tipe baru hanya dengan kunci yang ditentukan (K
) dari tipeT
.Omit
: Membuat tipe baru dengan semua properti dari tipeT
kecuali kunci yang ditentukan (K
).
Contoh: Menggunakan Pick
dan Omit
interface User {
id: number;
name: string;
email: string;
role: string;
}
type UserSummary = Pick;
// { id: number; name: string; }
type UserWithoutEmail = Omit;
// { id: number; name: string; role: string; }
Utility types ini menghindarkan Anda dari penulisan definisi mapped type yang berulang dan meningkatkan keterbacaan kode. Mereka sangat berguna dalam pengembangan global untuk mengelola tampilan atau tingkat akses data yang berbeda berdasarkan izin pengguna atau konteks aplikasi.
Aplikasi dan Contoh di Dunia Nyata
1. Validasi dan Transformasi Data
Mapped types sangat berharga untuk memvalidasi dan mengubah data yang diterima dari sumber eksternal (API, basis data, input pengguna). Ini sangat penting dalam aplikasi global di mana Anda mungkin berurusan dengan data dari banyak sumber yang berbeda dan perlu memastikan integritas data. Mereka memungkinkan Anda untuk mendefinisikan aturan spesifik, seperti validasi tipe data, dan secara otomatis memodifikasi struktur data berdasarkan aturan-aturan ini.
Contoh: Mengonversi Respons API
interface ApiResponse {
userId: string;
id: string;
title: string;
completed: boolean;
}
type CleanedApiResponse = {
[K in keyof ApiResponse]:
K extends 'userId' | 'id' ? number :
K extends 'title' ? string :
K extends 'completed' ? boolean : any;
};
Contoh ini mengubah properti userId
dan id
(yang awalnya string dari API) menjadi angka. Properti title
diketik dengan benar menjadi string, dan completed
dipertahankan sebagai boolean. Ini memastikan konsistensi data dan menghindari potensi kesalahan dalam pemrosesan selanjutnya.
2. Membuat Props Komponen yang Dapat Digunakan Kembali
Dalam React dan kerangka kerja UI lainnya, mapped types dapat menyederhanakan pembuatan props komponen yang dapat digunakan kembali. Ini sangat penting saat mengembangkan komponen UI global yang harus beradaptasi dengan berbagai lokal dan antarmuka pengguna.
Contoh: Menangani Lokalisasi
interface TextProps {
textId: string;
defaultText: string;
locale: string;
}
type LocalizedTextProps = {
[K in keyof TextProps as `localized-${K}`]: TextProps[K];
};
Dalam kode ini, tipe baru, LocalizedTextProps
, memberikan awalan pada setiap nama properti dari TextProps
. Misalnya, textId
menjadi localized-textId
, yang berguna untuk mengatur props komponen. Pola ini dapat digunakan untuk menghasilkan props yang memungkinkan perubahan teks secara dinamis berdasarkan lokal pengguna. Ini penting untuk membangun antarmuka pengguna multibahasa yang bekerja dengan lancar di berbagai wilayah dan bahasa, seperti dalam aplikasi e-commerce atau platform media sosial internasional. Props yang diubah memberi pengembang lebih banyak kontrol atas lokalisasi dan kemampuan untuk menciptakan pengalaman pengguna yang konsisten di seluruh dunia.
3. Pembuatan Formulir Dinamis
Mapped types berguna untuk menghasilkan bidang formulir secara dinamis berdasarkan model data. Dalam aplikasi global, ini dapat berguna untuk membuat formulir yang beradaptasi dengan peran pengguna atau persyaratan data yang berbeda.
Contoh: Menghasilkan bidang formulir secara otomatis berdasarkan kunci objek
interface UserProfile {
firstName: string;
lastName: string;
email: string;
phoneNumber: string;
}
type FormFields = {
[K in keyof UserProfile]: {
label: string;
type: string;
required: boolean;
};
};
Ini memungkinkan Anda untuk mendefinisikan struktur formulir berdasarkan properti dari antarmuka UserProfile
. Ini menghindari keharusan untuk mendefinisikan bidang formulir secara manual, meningkatkan fleksibilitas dan keterpeliharaan aplikasi Anda.
Teknik Mapped Type Tingkat Lanjut
1. Pemetaan Ulang Kunci (Key Remapping)
TypeScript 4.1 memperkenalkan pemetaan ulang kunci (key remapping) dalam mapped types. Ini memungkinkan Anda untuk mengganti nama kunci saat mengubah tipe. Ini sangat berguna saat mengadaptasi tipe untuk persyaratan API yang berbeda atau ketika Anda ingin membuat nama properti yang lebih ramah pengguna.
Contoh: Mengganti nama properti
interface Product {
productId: number;
productName: string;
productDescription: string;
price: number;
}
type ProductDto = {
[K in keyof Product as `dto_${K}`]: Product[K];
};
Ini mengganti nama setiap properti dari tipe Product
agar diawali dengan dto_
. Ini berharga saat memetakan antara model data dan API yang menggunakan konvensi penamaan yang berbeda. Hal ini penting dalam pengembangan perangkat lunak internasional di mana aplikasi berinteraksi dengan beberapa sistem back-end yang mungkin memiliki konvensi penamaan spesifik, memungkinkan integrasi yang lancar.
2. Pemetaan Ulang Kunci Kondisional
Anda dapat menggabungkan pemetaan ulang kunci dengan tipe kondisional untuk transformasi yang lebih kompleks, memungkinkan Anda untuk mengganti nama atau mengecualikan properti berdasarkan kriteria tertentu. Teknik ini memungkinkan transformasi yang canggih.
Contoh: Mengecualikan properti dari DTO
interface Product {
id: number;
name: string;
description: string;
price: number;
category: string;
isActive: boolean;
}
type ProductDto = {
[K in keyof Product as K extends 'description' | 'isActive' ? never : K]: Product[K]
}
Di sini, properti description
dan isActive
secara efektif dihapus dari tipe ProductDto
yang dihasilkan karena kunci tersebut diresolusi menjadi never
jika propertinya adalah 'description' atau 'isActive'. Ini memungkinkan pembuatan objek transfer data (DTO) spesifik yang hanya berisi data yang diperlukan untuk operasi yang berbeda. Transfer data selektif semacam itu sangat penting untuk optimisasi dan privasi dalam aplikasi global. Pembatasan transfer data memastikan bahwa hanya data yang relevan yang dikirim melintasi jaringan, mengurangi penggunaan bandwidth dan meningkatkan pengalaman pengguna. Hal ini sejalan dengan peraturan privasi global.
3. Menggunakan Mapped Types dengan Generics
Mapped types dapat digabungkan dengan generics untuk membuat definisi tipe yang sangat fleksibel dan dapat digunakan kembali. Ini memungkinkan Anda untuk menulis kode yang dapat menangani berbagai tipe yang berbeda, sangat meningkatkan kemampuan penggunaan kembali dan keterpeliharaan kode Anda, yang sangat berharga dalam proyek besar dan tim internasional.
Contoh: Fungsi Generik untuk Mengubah Properti Objek
function transformObjectValues(obj: T, transform: (value: T[K]) => U): {
[P in keyof T]: U;
} {
const result: any = {};
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
result[key] = transform(obj[key]);
}
}
return result;
}
interface Order {
id: number;
items: string[];
total: number;
}
const order: Order = {
id: 123,
items: ['apple', 'banana'],
total: 5.99,
};
const stringifiedOrder = transformObjectValues(order, (value) => String(value));
// stringifiedOrder: { id: string; items: string; total: string; }
Dalam contoh ini, fungsi transformObjectValues
menggunakan generics (T
, K
, dan U
) untuk mengambil objek (obj
) dari tipe T
, dan sebuah fungsi transformasi yang menerima satu properti dari T dan mengembalikan nilai dari tipe U. Fungsi tersebut kemudian mengembalikan objek baru yang berisi kunci yang sama dengan objek asli tetapi dengan nilai yang telah diubah menjadi tipe U.
Praktik Terbaik dan Pertimbangan
1. Keamanan Tipe dan Keterpeliharaan Kode
Salah satu manfaat terbesar dari TypeScript dan mapped types adalah peningkatan keamanan tipe. Dengan mendefinisikan tipe yang jelas, Anda menangkap kesalahan lebih awal selama pengembangan, mengurangi kemungkinan bug saat runtime. Mereka membuat kode Anda lebih mudah untuk dipahami dan direfaktor, terutama dalam proyek besar. Selain itu, penggunaan mapped types memastikan bahwa kode tidak terlalu rentan terhadap kesalahan saat perangkat lunak berskala besar, beradaptasi dengan kebutuhan jutaan pengguna secara global.
2. Keterbacaan dan Gaya Kode
Meskipun mapped types bisa sangat kuat, penting untuk menuliskannya dengan cara yang jelas dan mudah dibaca. Gunakan nama variabel yang bermakna dan berikan komentar pada kode Anda untuk menjelaskan tujuan dari transformasi yang kompleks. Kejelasan kode memastikan bahwa pengembang dari semua latar belakang dapat membaca dan memahami kode. Konsistensi dalam gaya, konvensi penamaan, dan pemformatan membuat kode lebih mudah didekati dan berkontribusi pada proses pengembangan yang lebih lancar, terutama dalam tim internasional di mana anggota yang berbeda mengerjakan bagian perangkat lunak yang berbeda.
3. Penggunaan Berlebihan dan Kompleksitas
Hindari penggunaan mapped types yang berlebihan. Meskipun kuat, mereka dapat membuat kode menjadi kurang mudah dibaca jika digunakan secara berlebihan atau ketika solusi yang lebih sederhana tersedia. Pertimbangkan apakah definisi antarmuka yang lugas atau fungsi utilitas sederhana mungkin merupakan solusi yang lebih tepat. Jika tipe Anda menjadi terlalu kompleks, akan sulit untuk dipahami dan dipelihara. Selalu pertimbangkan keseimbangan antara keamanan tipe dan keterbacaan kode. Mencapai keseimbangan ini memastikan bahwa semua anggota tim internasional dapat secara efektif membaca, memahami, dan memelihara basis kode.
4. Kinerja
Mapped types terutama memengaruhi pemeriksaan tipe saat kompilasi dan biasanya tidak menimbulkan overhead kinerja runtime yang signifikan. Namun, manipulasi tipe yang terlalu kompleks berpotensi memperlambat proses kompilasi. Minimalkan kompleksitas dan pertimbangkan dampaknya pada waktu build, terutama dalam proyek besar atau untuk tim yang tersebar di zona waktu yang berbeda dan dengan batasan sumber daya yang bervariasi.
Kesimpulan
Mapped types TypeScript menawarkan serangkaian alat yang kuat untuk mengubah bentuk objek secara dinamis. Mereka sangat berharga untuk membangun kode yang aman-tipe, mudah dipelihara, dan dapat digunakan kembali, terutama saat berhadapan dengan model data yang kompleks, interaksi API, dan pengembangan komponen UI. Dengan menguasai mapped types, Anda dapat menulis aplikasi yang lebih kuat dan mudah beradaptasi, menciptakan perangkat lunak yang lebih baik untuk pasar global. Untuk tim internasional dan proyek global, penggunaan mapped types menawarkan kualitas kode dan keterpeliharaan yang kuat. Fitur-fitur yang dibahas di sini sangat penting untuk membangun perangkat lunak yang dapat beradaptasi dan berskala, meningkatkan keterpeliharaan kode, dan menciptakan pengalaman yang lebih baik bagi pengguna di seluruh dunia. Mapped types membuat kode lebih mudah diperbarui ketika fitur, API, atau model data baru ditambahkan atau diubah.